# -*- coding: utf-8 -*-
import json
import traceback
from xml.dom import minidom

from django.utils.encoding import smart_unicode

from common import orm
from common.account.db import account as api_user
from common.account.model.account import ACCOUNT_STATUS
from common.admin import db as admin_user
from common.admin.model import RESOURCE, ACTION
from common.utils import JsonResponse, EnhencedEncoder
from common.utils import track_logging
from common.utils.api import get_client_ip, get_client_ua, parse_p
from common.utils.exceptions import Error
from common.utils.exceptions import PermissionError
from common.utils.respcode import StatusCodeDict
from common.utils.tz import local_now

_LOGGER = track_logging.getLogger(__name__)


def check_perm(url_array, perm, role, user_perm):
    user_perm_attrs = user_perm.split('|')
    length = len(url_array)
    while length >= 1:
        url = '/'.join(url_array[0:length])
        if not url.endswith('/'):
            url += '/'
        k = admin_user.get_perm(url, perm)
        if k and k.min_role <= role:
            if k.user_perm == '0':
                return True
            k_user_perm_attr = k.user_perm.split('|')
            # if set(k_user_perm_attr).issubset(set(user_perm_attrs)):
            #     return True
            for user_perm in user_perm_attrs:
                if user_perm != '' and user_perm in k_user_perm_attr:
                    return True
        length -= 1
    return False


class UserMiddleware(object):
    """get user_id and token from header"""

    def process_request(self, req):
        user_id, token = req.META.get(
            'HTTP_X_AUTH_USER'), req.META.get('HTTP_X_AUTH_TOKEN')
        if not user_id:
            user_id, token = req.COOKIES.get(
                'bigbang_user_id'), req.COOKIES.get(
                'bigbang_user_token')
        if user_id and token:
            try:
                user_id = long(user_id)
            except ValueError:
                req.user_id = req.user = None
                return
            if req.path.startswith('/api'):
                user = api_user.get_account(user_id)
                # check banned
                if not user or user.status == ACCOUNT_STATUS.BANNED:
                    # raise PermissionError('forbidden')
                    req.user_id = req.user = None
                    return
                info = api_user.get_online_info(user_id, token)
                if info and info.deleted == 0:
                    req.user_id = user_id
                    for k in 'token', 'extend':
                        v = getattr(info, k)
                        setattr(user, k, v)
                    req.user = user
                    return
            else:
                info = admin_user.get_online_info(user_id, token)
                if info and info.deleted == 0:
                    req.user_id = user_id
                    user = admin_user.get_user(user_id)
                    if user and user.role > 0:
                        url_array = req.path.split('/')
                        if req.method == 'GET':
                            need_perm = 1
                        else:
                            need_perm = 2
                        if not check_perm(url_array, need_perm, user.role, user.perm):
                            return JsonResponse(dict(
                                status=PermissionError.STATUS,
                                msg=str('permission not enough')),
                                status=PermissionError.HTTPCODE)
                        else:
                            user.user_name = user.nickname  # 方便后面打印日志统一处理
                            req.user = user
                            return
                    else:
                        return JsonResponse(dict(
                            status=PermissionError.STATUS,
                            msg=str("user is forbidden or not activited")),
                            status=PermissionError.HTTPCODE)

        req.user_id = req.user = None

    def process_response(self, req, resp):
        if req.method not in ACTION:
            return resp
        resource_array = req.path.split('/')
        if resource_array[0] == '':
            resource_array.pop(0)
        if resource_array[0] == 'admin':
            resource_array.pop(0)
        if resource_array[-1] == '':
            resource_array.pop(-1)
        try:
            resource_id = long(resource_array[-1])
            resource_array.pop(-1)
        except ValueError:
            resource_id = None
        resource = '/'.join(resource_array)
        if resource not in RESOURCE:
            return resp
        else:
            resource = RESOURCE[resource]

        try:
            content = json.loads(resp.content)
            status = content['status']
            if status != 0:
                return resp
            data = content['data']
            if isinstance(data, dict):
                if 'id' in data:
                    resource_id = data['id']
                if resource_id is not None:
                    record = {
                        'resource': resource,
                        'resource_id': resource_id,
                        'action': ACTION[req.method],
                        'content': req.body,
                        'operator': req.user_id
                    }
                    admin_user.insert_record(record)
            elif isinstance(data, list):
                for d in data:
                    if 'id' not in d:
                        continue
                    else:
                        admin_user.insert_record({
                            'resource': resource,
                            'resource_id': d['id'],
                            'action': ACTION[req.method],
                            'content': json.dumps(d, ensure_ascii=False,
                                                  cls=EnhencedEncoder),
                            'operator': req.user_id
                        })
        except (ValueError, TypeError) as e:
            _LOGGER.exception(e)

        return resp


class ApiGateMiddleware(object):
    """GateMiddleware

    Gate,大门,所有API访问的进出口.
    该middleware应该放在middlewares的第一位

    该middleware做了以下事情:
    为每个request编号;
    拦截所有request进入, 把trace_id和ip,device,data等信息打印到log;
    拦截所有request出去, 把trace_id和此次访问所耗时间打印到log;
    """

    def process_request(self, request):
        try:
            track_logging.clear_trace()
            request.incoming_time = local_now()
            request.ip = get_client_ip(request)
            request.device = get_client_ua(request)
            request.p = parse_p(request.GET.get('p'))

            # 生成request.DATA
            request.DATA = None
            if request.method == 'GET':
                request.DATA = request.GET
            else:
                body = request.body
                if body:
                    if request.META.get('CONTENT_TYPE', '').startswith('application/json'):
                        request.DATA = json.loads(smart_unicode(body))
                    elif 'xml' in request.META.get('CONTENT_TYPE', ''):
                        request.DATA = minidom.parseString(smart_unicode(body))
                    else:
                        request.DATA = smart_unicode(body)
        except:
            _LOGGER.exception('middleware process request fail')

    def process_view(self, request, view_func, view_args, view_kwargs):
        """打印request进入时的log"""
        try:
            user = request.user and '%s(%s)' % (request.user.id, request.user.user_name)
            _LOGGER.info("%s - %s, USER: %s, DATA: %s, IP: %s, DEVICE: %s, P: %s" % (
                request.method.upper(), request.path, user, request.DATA, request.ip, request.device, request.p))
        except:
            _LOGGER.exception('middleware process view fail')

    def process_response(self, request, response):
        """拦截所有request出去, 把trace_id和此次访问所耗时间打印到log"""
        try:
            orm.session.close()
            if request.method.upper() == 'OPTIONS':
                return response

            duration = int((local_now() - request.incoming_time).total_seconds() * 1000)
            _LOGGER.info("URL: %s: %s, Duration:%s" % (request.method.upper(), request.path, duration))
            # 对慢请求做出错报警
            if duration >= 6000:
                _LOGGER.error("slow request, url: %s, duration: %s" % (request.path, duration))
        except:
            _LOGGER.exception('middleware process response fail')

        return response

    def process_exception(self, request, e):
        try:
            if isinstance(e, Error):
                _LOGGER.info('server error! %s %s %s', e.HTTPCODE,
                             unicode(e) or StatusCodeDict.get(e.STATUS),
                             request.path)
                return JsonResponse(
                    dict(status=e.STATUS,
                         msg=unicode(e) or StatusCodeDict.get(e.STATUS)),
                    status=e.HTTPCODE)
            else:
                """拦截所有未处理的exception,并把traceback转化为一行数据打印到log"""
                user_repr = "%s (%s)" % (request.user.id, request.user.user_name) if request.user else ''

                _LOGGER.error(
                    'REQUEST URL: %s, USER: %s, Unexpected Exception: %s' % (
                        request.path, user_repr, traceback.format_exc()))
        except:
            _LOGGER.exception('middleware process exception fail')
